use std::collections::HashMap;

use common_base::serde::{self, Serialize};

use crate::data::{AsRowField, DatasetOwner, RowField, RowsetOwner};

use super::{
    biz_metadata::BizMetadata, dct_metadata::DctMetadata, do_metadata::DoMetadata, FctMetadata,
};

#[derive(Debug, Clone, Copy)]
pub enum MetadataType {
    Biz,
    Dct,
    Fct,
    Do,
}

#[derive(Debug, Clone)]
// #[derive( Serialize, )]
// #[serde(crate = "self::serde")]
// #[serde(rename_all = "camelCase")]
pub enum Metadata {
    Biz(BizMetadata),
    Dct(DctMetadata),
    Do(DoMetadata),
    Fct(FctMetadata),
    // IoMetadata
}

impl AsRowField for Metadata {
    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn object_clone(&self) -> Box<dyn AsRowField> {
        Box::new(self.clone())
    }
}

impl Into<RowField> for Metadata {
    fn into(self) -> RowField {
        RowField::Object(Box::new(self))
    }
}

impl Metadata {
    pub fn as_biz_metadata(&self) -> Option<&BizMetadata> {
        if let Self::Biz(inner) = self {
            Some(inner)
        } else {
            None
        }
    }
    pub fn as_do_metadata(&self) -> Option<&DoMetadata> {
        if let Self::Do(inner) = self {
            Some(inner)
        } else {
            None
        }
    }
    pub fn as_do_metadata_mut(&mut self) -> Option<&mut DoMetadata> {
        if let Self::Do(inner) = self {
            Some(inner)
        } else {
            None
        }
    }
}

impl Metadata {
    pub fn get_metadata_type(&self) -> MetadataType {
        match self {
            Metadata::Biz(_) => MetadataType::Biz,
            Metadata::Dct(_) => MetadataType::Dct,
            Metadata::Fct(_) => MetadataType::Fct,
            Metadata::Do(_) => MetadataType::Do,
        }
    }

    pub fn get_object_id(&self) -> &str {
        match self {
            Metadata::Biz(inner) => &inner.base.object_id,
            Metadata::Dct(inner) => &inner.base.object_id,
            Metadata::Fct(inner) => &inner.base.object_id,
            Metadata::Do(inner) => &inner.base.object_id,
        }
    }

    pub fn set_db_name(&mut self, db_name: &str) {
        match self {
            Metadata::Biz(inner) => inner.base.db_name = db_name.to_owned(),
            Metadata::Dct(inner) => inner.base.db_name = db_name.to_owned(),
            Metadata::Fct(inner) => inner.base.db_name = db_name.to_owned(),
            Metadata::Do(inner) => inner.base.db_name = db_name.to_owned(),
        }
    }

    pub fn set_db_no(&mut self, db_no: &str) {
        match self {
            Metadata::Biz(inner) => inner.base.db_no = db_no.to_owned(),
            Metadata::Dct(inner) => inner.base.db_no = db_no.to_owned(),
            Metadata::Fct(inner) => inner.base.db_no = db_no.to_owned(),
            Metadata::Do(inner) => inner.base.db_no = db_no.to_owned(),
        }
    }

    pub fn process_extend_property(
        metadata: &mut MetadataBase,
        dataset: &DatasetOwner,
        unit_col: &str,
        key_col: &str,
        val_col: &str,
    ) {
        for rowset in dataset.get_rowset_list() {
            metadata.put_extend_property(
                rowset.get_string(unit_col),
                rowset.get_string(key_col),
                rowset.get_string(val_col),
            )
        }
    }
}

#[derive(Debug, Serialize, Clone)]
#[serde(crate = "self::serde")]
#[serde(rename_all = "camelCase")]
pub struct MetadataBase {
    pub(crate) object_id: String,
    pub(crate) ref_id: usize,
    pub(crate) db_name: String,
    pub(crate) db_no: String,
    #[serde(skip)]
    pub(crate) self_rowset: RowsetOwner,
    pub(crate) extend_property: HashMap<String, HashMap<String, String>>,
}

const INNR_UNIT: &'static str = "_INNR_UNIT_";
impl MetadataBase {
    pub fn new(object_id: &str, rowset: RowsetOwner) -> Self {
        Self {
            object_id: object_id.to_owned(),
            ref_id: usize::MAX,
            db_name: Default::default(),
            db_no: Default::default(),
            self_rowset: rowset,
            extend_property: HashMap::default(),
        }
    }

    pub fn get_extend_property(&self, unit_id: &str) -> Option<&HashMap<String, String>> {
        if self.extend_property.is_empty() {
            None
        } else {
            if unit_id.is_empty() || unit_id.trim().is_empty() {
                self.extend_property.get(INNR_UNIT)
            } else {
                self.extend_property.get(unit_id)
            }
        }
    }

    pub fn put_extend_property(&mut self, unit_id: &str, key: &str, value: &str) {
        let unit_id = if unit_id.trim().is_empty() {
            "_INNR_UNIT_"
        } else {
            unit_id
        };

        if !self.extend_property.contains_key(unit_id) {
            self.extend_property
                .insert(unit_id.to_owned(), HashMap::default());
        }

        self.extend_property
            .get_mut(unit_id)
            .unwrap()
            .insert(key.to_owned(), value.to_owned());
    }
}
